package com.example.design.uploadfile.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.example.design.uploadfile.bean.SegmentFileDO;
import com.example.design.uploadfile.bean.UpLoadSegmentFileAO;
import com.example.design.uploadfile.mapper.SegmentFileMapper;
import com.example.design.uploadfile.service.SegmentFileService;
import com.example.design.uploadfile.util.FileUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;

import javax.annotation.Resource;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.ArrayList;

@Slf4j
@Service
public class SegmentFileServiceImpl implements SegmentFileService {
    @Resource
    SegmentFileMapper segmentFileMapper;

    // 该文件存在，返回数据
    @Override
    public SegmentFileDO getSegmentFileByKey(String key) {
        QueryWrapper<SegmentFileDO> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("md5_key", key);
        return segmentFileMapper.selectOne(queryWrapper);
    }

    // 存储分片到服务器
    @Override
    public SegmentFileDO saveSegment(String savePath, UpLoadSegmentFileAO upLoadSegmentFileAO) {
        // 上传文件分片
        String saveFileName = FileUtil.createSaveFileName(upLoadSegmentFileAO.getKey(), upLoadSegmentFileAO.getOriginFileName());
        String segmentFileName = FileUtil.getSegmentName(saveFileName, upLoadSegmentFileAO.getSegmentIndex());
        upload(upLoadSegmentFileAO.getFile(), savePath + segmentFileName);

        // 分片信息写入db
        SegmentFileDO segmentFileDO = this.getSegmentFileByKey(upLoadSegmentFileAO.getKey());
        if (segmentFileDO == null) {
            segmentFileDO = this.createSegmentFile(savePath, upLoadSegmentFileAO);
        } else {
            increaseSegmentIndex(upLoadSegmentFileAO);
            segmentFileDO.setSegmentIndex(segmentFileDO.getSegmentIndex() + 1);
        }

        // 分片合并
        if (upLoadSegmentFileAO.getSegmentIndex() == segmentFileDO.getSegmentTotal()) {
            this.mergeSegment(segmentFileDO);
            try {
                this.deleteSegments(upLoadSegmentFileAO.getKey());
            } catch (InterruptedException e) {
                log.error("删除分片失败：{}", e);
                throw new RuntimeException("删除分片失败!");
            }
        }
        return segmentFileDO;
    }

    /**
     * 新增分片记录
     * @param savePath
     * @param upLoadSegmentFileAO
     * @return
     */
    public SegmentFileDO createSegmentFile(String savePath, UpLoadSegmentFileAO upLoadSegmentFileAO) {
        String fileName = FileUtil.createUUIDFileName(upLoadSegmentFileAO.getOriginFileName());
        String saveFileName = FileUtil.createSaveFileName(upLoadSegmentFileAO.getKey(), upLoadSegmentFileAO.getOriginFileName());
        SegmentFileDO segmentFileDO = new SegmentFileDO();
        // filepath为完整路径
        segmentFileDO.setFilePath(savePath + saveFileName);
        segmentFileDO.setFileName(fileName);
        segmentFileDO.setSize(upLoadSegmentFileAO.getFileSize());
        segmentFileDO.setSegmentIndex(upLoadSegmentFileAO.getSegmentIndex());
        segmentFileDO.setSegmentSize(upLoadSegmentFileAO.getSegmentSize());

        int total = upLoadSegmentFileAO.getFileSize() / upLoadSegmentFileAO.getSegmentSize();
        if (upLoadSegmentFileAO.getFileSize() % upLoadSegmentFileAO.getSegmentSize() != 0) {
            total++;
        }
        segmentFileDO.setSegmentTotal(total);
        segmentFileDO.setMd5Key(upLoadSegmentFileAO.getKey());
        segmentFileMapper.insert(segmentFileDO);
        return segmentFileDO;
    }

    /**
     * 分片索引++
     */
    private void increaseSegmentIndex(UpLoadSegmentFileAO upLoadSegmentFileAO) {
        UpdateWrapper wrapper = new UpdateWrapper();
        wrapper.eq("md5_key", upLoadSegmentFileAO.getKey());
        wrapper.setSql(" segment_index = segment_index + 1");
        segmentFileMapper.update(null, wrapper);
    }

    /**
     * 合并分片
     * @param segmentFileDO
     */
    public void mergeSegment(SegmentFileDO segmentFileDO) {
        int segmentCount = segmentFileDO.getSegmentTotal();
        FileInputStream fileInputStream = null;
        FileOutputStream outputStream = null;
        byte[] byt = new byte[10 * 1024 * 1024];
        try {
            // 整合结果文件
            File newFile = new File(segmentFileDO.getFilePath());
            outputStream = new FileOutputStream(newFile, true);
            int len;
            for (int i = 0; i < segmentCount; i++) {
                String segmentFilePath = FileUtil.getSegmentName(segmentFileDO.getFilePath(), i + 1);
                fileInputStream = new FileInputStream(new File(segmentFilePath));
                while ((len = fileInputStream.read(byt)) != -1) {
                    outputStream.write(byt, 0, len);
                }
            }
        } catch (IOException e) {
            log.error("合并失败：{}", e);
            throw new RuntimeException("合并失败");
        } finally {
            try {
                if (fileInputStream != null) {
                    fileInputStream.close();
                }
                if (outputStream != null) {
                    outputStream.close();
                }
            } catch (Exception e) {
                log.error("关流失败：{}", e);
                throw new RuntimeException("关流失败!");
            }
        }
    }

    /**
     * 删除分片
     */
    public void deleteSegments(String key) throws InterruptedException {
        // 为了保证不被占用，先回收数据流对象
        System.gc();
        Thread.sleep(1000);
        SegmentFileDO segmentFileByKey = this.getSegmentFileByKey(key);
        int segmentCount = segmentFileByKey.getSegmentTotal();
        ArrayList<String> remain = new ArrayList<>();
        int finished = 0;
        int[] visited = new int[segmentCount];
        for (int i = 0; i < segmentCount; i++) {
            String segmentFilePath = FileUtil.getSegmentName(segmentFileByKey.getFilePath(), i + 1);
            remain.add(segmentFilePath);
            File file = new File(segmentFilePath);
            boolean result = file.delete();
            if (result) {
                finished++;
                visited[i] = 1;
            }
            log.info("分片文件:" + segmentFilePath + "删除" + (result ? "成功" : "失败"));
        }
        // visited数组，然后完成了再去除，知道count到达总数
        while (finished < segmentCount) {
            System.gc();
            Thread.sleep(1000);
            for (int i = 0; i < segmentCount; i++) {
                if (visited[i] == 0) {
                    String segmentFilePath = FileUtil.getSegmentName(segmentFileByKey.getFilePath(), i + 1);
                    remain.add(segmentFilePath);
                    File file = new File(segmentFilePath);
                    boolean result = file.delete();
                    if (result) {
                        visited[i] = 1;
                        finished++;
                    }
                    log.info("分片文件:" + segmentFilePath + "删除" + (result ? "成功" : "失败"));
                }
            }
        }
    }

    /**
     * 上传分片
     */
    private boolean upload(MultipartFile file, String path) {
        System.out.println(path);
        File dest = new File(path);
        //判断文件父目录是否存在

        if (!dest.getParentFile().exists()) {
            boolean b = dest.getParentFile().mkdir();
            if (!b) {
                return b;
            }
        }
        //保存文件
        try {
            file.transferTo(dest);
            return true;
        } catch (IllegalStateException | IOException e) {
            e.printStackTrace();
            return false;
        }
    }
}
